/**
 * 
 */
package cu.ds.logicalsync.fytasrealm.communication;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;

import cu.ds.logicalsync.fytasrealm.communication.model.PeerInfo;
import net.jxta.credential.AuthenticationCredential;
import net.jxta.credential.Credential;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.StructuredDocument;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.IDFactory;
import net.jxta.membership.Authenticator;
import net.jxta.membership.MembershipService;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.platform.Module;
import net.jxta.platform.NetworkManager;
import net.jxta.protocol.ModuleImplAdvertisement;
import net.jxta.protocol.PeerGroupAdvertisement;
import net.jxta.protocol.PipeAdvertisement;

/**
 * JxtaConnection is a singleton/facade to shield low-level JXTA connection from application level
 *  
 * @author r.vashira
 */
public class JxtaConnection {
	/** Constants **/
	public static final String FYTAS_REALM_GROUP = "FytasRealm";
	public static final String FYTAS_REALM_GROUP_DESC = "Fyta's Realm Game Network - LogicalSync";
	public static final String MAP_PIPE = "MAP_PIPE";
	public static final String MAP_PIPE_ID_STR = "urn:jxta:uuid-4679746173524561AC6D8000000000004D6170506970450080008004";


	/** Member Variable **/
	/** Singleton **/
	private static JxtaConnection instance = null;
	
	/** All PeerGroup over JXTA Network */
	private PeerGroup netPeerGroup = null;
	/** Fyta's Realm PeerGroup */
	private PeerGroup peerGroup = null;
	
	/**
	 * Connection point to all message passing related to MapInfo events
	 */
	private PipeAdvertisement mapPipeAdvertisement = null;
	private PipeAdvertisement worldPipeAdvertisement = null;

	private transient NetworkManager manager = null;
	private transient DiscoveryService discoveryService = null;
	private transient PipeService pipeService = null;

	private boolean online = false;

	private String myPeerName;
	private PeerID myPeerID;
	private PeerGroupID myPeerGroupID;

	/** Properties **/
	/**
	 * Is client connect to JXTA Network
	 * @return <code>true</code> if connected, otherwise <code>false</code>
	 */
	public boolean isOnline() {
		return online;
	}

	/**
	 * get my peer name
	 * @return current client name
	 */
	public String getMyPeerName() {
		return this.myPeerName;
	}
	
	/**
	 * get my peer identification which is unique across the system
	 * @return PeerID for current client
	 */
	public PeerID getMyPeerID() {
		return this.myPeerID;
	}
	
	/**
	 * get Fyta's Realm Group ID
	 * @return
	 */
	public PeerGroupID getMyPeerGroupID() {
		return this.myPeerGroupID;
	}
	
	/**
	 * Get JXTA DiscoveryService
	 * @return
	 */
	public DiscoveryService getDiscoveryService() {
		return this.discoveryService;
	}
	
	/**
	 * Get JXTA PipeService
	 * @return
	 */
	public PipeService getPipeService() {
		return this.pipeService;
	}
	
	/**
	 * Get connection point to all message passing related to MapInfo events
	 * @return
	 */
	public PipeAdvertisement getMapPipeAdvertisement() {
		return mapPipeAdvertisement;
	}
	
	/**
	 * Get singleton instance
	 * @param clientName
	 * @return the singleton instance
	 */
	public static JxtaConnection getInstance(String clientName) {
		if(instance == null) {
			instance = new JxtaConnection(clientName);
		}
		return instance;
	}
	
	/**
	 * Singleton Constructor
	 * 
	 * @param myPeerName name of current client
	 */
	private JxtaConnection(String myPeerName) {

		this.myPeerName = myPeerName;
		this.myPeerID = initializeMyPeerID(myPeerName);
		this.myPeerGroupID = initializePeerGroupID(FYTAS_REALM_GROUP);

		try {
			//TODO: Parse configuration from xml
			manager = new NetworkManager(NetworkManager.ConfigMode.ADHOC, 
					this.myPeerName,
					new File(new File(".jxta"), FYTAS_REALM_GROUP).toURI());

			manager.setPeerID(myPeerID);
			//Tools.popInformationMsg(FYTAS_REALM_GROUP, "Setting the peer ID:\n\n" + myPeerID.toString());

			manager.startNetwork();
			//Tools.popInformationMsg(FYTAS_REALM_GROUP, "Starting the JXTA network");
		} catch (IOException e) {
			e.printStackTrace();
		} catch (PeerGroupException e) {
			e.printStackTrace();
		}
		
		try {
			goOnline();
		} catch (CommunicationException e) {
			e.printStackTrace();
		}
		
		// initial all connection points
		this.mapPipeAdvertisement = initializeMapPipeAdvertisement();
	}
	

	/** Public Method **/
	/** 
	 * Online to JXTA Network
	 * @throws CommunicationException
	 */
	private void goOnline() throws CommunicationException {
		this.online = false;
		
		// Get the NetPeerGroup
		netPeerGroup = manager.getNetPeerGroup();
		// Create Game Group (Application Group) and also init discoveryService
		peerGroup = createGameGroup(netPeerGroup);
		// Join the Game Group
		joinGroup(peerGroup);

		// Set Services
		this.pipeService = peerGroup.getPipeService();
		
		this.online = true;
	}
	
	/**
	 * Offline from JXTA Network
	 */
	public void goOffline() {
		if (online) {
			peerGroup = null;
			online = false;
		}
		manager.stopNetwork();
	}
	
	/**
	 * Add listener to JxtaConnection - DiscoveryService
	 * @param listener
	 */
	public void addDiscoveryServiceListener(DiscoveryListener listener) {
		this.discoveryService.addDiscoveryListener(listener);
	}
	
	/** Helper Method **/
	/**
	 * CALL THIS ONCE
	 * @param myPeerName
	 * @return
	 */
	private PeerID initializeMyPeerID(String myPeerName) {
		return IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID, myPeerName.getBytes());
	}
	
	/**
	 * CALL THIS ONCE
	 * @param myPeerGroupName
	 * @return
	 */
	private PeerGroupID initializePeerGroupID(String myPeerGroupName) {
		return IDFactory.newPeerGroupID(myPeerGroupName.getBytes());
	}
	
	private PipeAdvertisement initializeMapPipeAdvertisement() {
		PipeID pipeID = null;

        try {
            pipeID = (PipeID) IDFactory.fromURI(new URI(MAP_PIPE_ID_STR));
        } catch (URISyntaxException use) {
            use.printStackTrace();
        }
        PipeAdvertisement advertisement = (PipeAdvertisement)
                AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());

        advertisement.setPipeID(pipeID);
        advertisement.setType(PipeService.UnicastType);
        advertisement.setName(MAP_PIPE);
        return advertisement;
	}
	
	/**
	 * Create Fyta's Relam game group
	 * @param netPeerGroup
	 * @return
	 */
	private PeerGroup createGameGroup(PeerGroup netPeerGroup) {
		PeerGroupAdvertisement adv = null;
		PeerGroup pg = null;
		
		try {
			ModuleImplAdvertisement implAdv =
			    netPeerGroup.getAllPurposePeerGroupImplAdvertisement();
			
			// The creation includes local publishing
			pg = netPeerGroup.newGroup(
					this.myPeerGroupID, 
					implAdv,
					FYTAS_REALM_GROUP,
					FYTAS_REALM_GROUP_DESC);
			//Tools.popInformationMsg(FYTAS_REALM_GROUP, "Creating the new peer group:\n\n" + FYTAS_REALM_GROUP);
			
			// Don't forget to start your peer group
            if (Module.START_OK == pg.startApp(new String[0])) {
            	// TODO: System Log - Create FytaRealm Group in JXTA Network successfully. 
                //Tools.popInformationMsg(FYTAS_REALM_GROUP, "New peer group started successfully");
            } else {
            	// TODO: System Log - FAIL Create FytaRealm Group in JXTA Network.  
                //Tools.popInformationMsg(FYTAS_REALM_GROUP, "New peer group NOT started successfully !!!");
            }
			
			// Initial DiscoveryService
			this.discoveryService = pg.getDiscoveryService();
			// Add ourselves as a listener.
			//discoveryService.addDiscoveryListener(this);
			
			// Publish PeerGroupAdvertisement so other client able to join
			adv = pg.getPeerGroupAdvertisement();
			discoveryService.publish(adv);
			discoveryService.remotePublish(adv);
	        //TODO: Logging
	        System.out.println("Group '" + adv.getName() + "' published successfully.");
	        
		} catch (Exception e) {
			e.printStackTrace();
			// Unhandle exception
			throw new RuntimeException(e.getMessage());
		}
		
		return pg;
	}
	
	/**
	 * Join Fyta's Relam game Group in JXTA Network
	 * @param grp
	 * @throws CommunicationException
	 */
	private void joinGroup(PeerGroup grp) throws CommunicationException {
		StructuredDocument creds = null;
		try {
			// Generate the credentials for the Peer Group
			AuthenticationCredential authCred = new AuthenticationCredential(grp, null, creds);

			// Get the MembershipService from the peer group
			MembershipService membership = grp.getMembershipService();

			// Get the Authenticator from the Authentication creds
			Authenticator auth = membership.apply(authCred);

			// Check if everything is okay to join the group
			if (auth.isReadyForJoin()) {
				Credential myCred = membership.join(auth);
			} else {
				throw new CommunicationException("Unable to join PeerGroup");
			}
			//log.debug("PeerGroup joint");
			System.out.println("PeerGroup joint");
		} catch (Exception e) {
			//log.error(e);
			throw new CommunicationException("Unable to join PeerGroup", e);
		}
	}
}
